#include "Vec4.hpp"
#include "Vec3.hpp"
#include <array>
#include <cmath>
#include <iostream>
#include <sstream>

using std::ostream;
using std::ostringstream;
using std::sqrt;
using std::array;

namespace gmath
{

  template<class T>
    Vec4<T>::Vec4()
    {
      data_.fill(0);
    }

  template<class T>
    Vec4<T>::Vec4(T value)
    {
      setData(value);
    }

  template<class T>
    Vec4<T>::Vec4(T x, T y, T z, T w)
    {
      setData(x, y, z, w);
    }

  template<class T>
    Vec4<T>::Vec4(Vec3<T>& v, T w)
    {
      setData(v, w);
    }

  template<class T>
    Vec4<T>::Vec4(T* ptr)
    {
      setData(ptr);
    }

  template<class T>
    const std::array<T, 4>& gmath::Vec4<T>::getData() const
    {
      return data_;
    }

  template<class T>
    void gmath::Vec4<T>::setData(const std::array<T, 4> data)
    {
      data_ = data;
    }

  template<class T>
    inline void gmath::Vec4<T>::setData(T value)
    {
      data_.fill(value);
    }

  template<class T>
    inline void gmath::Vec4<T>::setData(T x, T y, T z, T w)
    {
      data_[0] = x;
      data_[1] = y;
      data_[2] = z;
      data_[3] = w;
    }

  template<class T>
    inline void gmath::Vec4<T>::setData(T* ptr)
    {
      for (short i = 0; i < 4; i++)
        data_[i] = ptr[i];
    }

  template<class T>
    void gmath::Vec4<T>::setData(Vec3<T>& v, T w)
    {
      data_[0] = v.getData()[0];
      data_[1] = v.getData()[1];
      data_[2] = v.getData()[2];
      data_[3] = w;
    }

  template<class T>
    T Vec4<T>::length()
    {
      return sqrt(
          data_[0] * data_[0] + data_[1] * data_[1] + data_[2] * data_[2]
              + data_[3] * data_[3]);
    }

  template<class T>
    T Vec4<T>::dot(Vec4<T>& v2)
    {
      return data_[0] * v2.data_[0] + data_[1] * v2.data_[1]
          + data_[2] * v2.data_[2] + data_[3] * v2.data_[3];
    }

  template<class T>
    std::string Vec4<T>::toString()
    {
      std::ostringstream ss;
      ss << "{" << data_[0] << ", " << data_[1] << ", " << data_[2] << ", "
          << data_[3] << "}";
      return ss.str();
    }

  template<typename T>
    std::ostream& operator<<(std::ostream& out, Vec4<T> v)
    {
      out << v.toString();
      return out;
    }

  template<class T>
    Vec4<T> Vec4<T>::operator+(Vec4<T>& v2)
    {
      return Vec4<T>(data_[0] + v2.data_[0], data_[1] + v2.data_[1],
          data_[2] + v2.data_[2], data_[3] + v2.data_[3]);
    }

  template<class T>
    Vec4<T> Vec4<T>::operator-(Vec4<T>& v2)
    {
      return Vec4<T>(data_[0] - v2.data_[0], data_[1] - v2.data_[1],
          data_[2] - v2.data_[2], data_[3] - v2.data_[3]);
    }

  template<class T>
    Vec4<T> Vec4<T>::operator*(T a)
    {
      return Vec4<T>(data_[0] * a, data_[1] * a, data_[2] * a, data_[3] * a);
    }

  template<class T>
    Vec4<T> Vec4<T>::operator/(T a)
    {
      return Vec4<T>(data_[0] / a, data_[1] / a, data_[2] / a, data_[3] / a);
    }

  template<class T>
    Vec4<T> Vec4<T>::operator+=(Vec4<T>& v2)
    {
      data_[0] += v2.data_[0];
      data_[1] += v2.data_[1];
      data_[2] += v2.data_[2];
      data_[3] += v2.data_[3];
      return *this;
    }

  template<class T>
    Vec4<T> Vec4<T>::operator-=(Vec4<T>& v2)
    {
      data_[0] -= v2.data_[0];
      data_[1] -= v2.data_[1];
      data_[2] -= v2.data_[2];
      data_[3] -= v2.data_[3];
      return *this;
    }

  template<class T>
    Vec4<T> Vec4<T>::operator*=(T a)
    {
      data_[0] *= a;
      data_[1] *= a;
      data_[2] *= a;
      data_[3] *= a;
      return *this;
    }

  template<class T>
    Vec4<T> Vec4<T>::operator/=(T a)
    {
      data_[0] /= a;
      data_[1] /= a;
      data_[2] /= a;
      data_[3] /= a;
      return *this;
    }

  template ostream& operator<<(ostream& out, Vec4<float> v);
  template ostream& operator<<(ostream& out, Vec4<double> v);
  template ostream& operator<<(ostream& out, Vec4<int> v);
  template class Vec4<float> ;
  template class Vec4<double> ;
  template class Vec4<int> ;
}
